package eval.engine.FireFighting;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.google.inject.Inject;
import eval.deploy.DeploySet;
import eval.deploy.IDemandManager;
import eval.deploy.IDeployManager;
import eval.engine.IPlacement;
import eval.engine.IPlacementEngine;
import eval.engine.BasicEngineModel;
import eval.host.Host;
import eval.host.IHostManager;
import eval.host.compare.ISortHostList;
import eval.hostEvaluate.IHostEvaluate;
import eval.vm.IVMManager;
import eval.vm.MinCPURequiredComparator;
import eval.vm.VirtualMachine;

public class FireFightingEngine extends BasicEngineModel implements IPlacementEngine {

	boolean isOngoingOptimizationTick;
	List<VirtualMachine> vmRelocate;
	Map<VirtualMachine, Integer> cpuDemandOfVMs;
	Map<VirtualMachine, Integer> memDemandOfVMs;
	/**
	 * @Constructor
	 * @param vmManager
	 * @param deployManager
	 * @param hostManager
	 * @param demandManager
	 * @param sortHostList
	 * @param hostEvaluator
	 */
	@Inject
	public FireFightingEngine(IVMManager vmManager, IDeployManager deployManager,
			IHostManager hostManager, IDemandManager demandManager,
			ISortHostList sortHostList,IHostEvaluate hostEvaluator) {
		super(vmManager,deployManager,hostManager,demandManager,sortHostList,hostEvaluator);
		
		
	}



	@Override
	public double getEngineTimeInSec() {
		return engineStopTime - engineStartTime;
	}



	@Override
	public void resetEngineTime() {
		engineStartTime = engineStopTime = 0;
		
	}



	@Override
	public IPlacement computePlacement(int tick, IPlacement lastPlacement) {
		//update start time
		engineStartTime = System.currentTimeMillis();
		this.tick = tick;
		//calc cycle optimization
		isOngoingOptimizationTick = (tick % configuration.ongoingOptimizationPeriod) == 0;
		
		vmRelocate = new ArrayList<VirtualMachine>();
		//list of vm's that going to be deploy  
		List<VirtualMachine> placedNewVms =  new ArrayList<VirtualMachine>();
		//list of vm's that ask to deploy but rejected.
		List<VirtualMachine> rejectedVms =  new ArrayList<VirtualMachine>();
		//remove vm's list
		List<VirtualMachine> vmsToRemove = deployManager.getVMToBeRemovedAtTick(tick ,vmMapping);
		//deploy new vm list
		DeploySet deploySet = deployManager.getDeploySetAtTick(tick);
		//new deploy vm's
		List<VirtualMachine> vmsToDeploy = deploySet.getVMs();
		//CPU demand of vm's already on cloud
		cpuDemandOfVMs = demandManager.getCPUDemandOfAllVMsAtTick(tick);
		//MEM demand of vm's already on cloud
		memDemandOfVMs = demandManager.getMEMDemandOfAllVMsAtTick(tick);
		//remove vm from host
		removeVirtualMachines(vmsToRemove);
		//Initial vmMaping and cloud
		iniCloudMapping(lastPlacement,vmsToRemove,cpuDemandOfVMs,memDemandOfVMs);
		if (!deploySet.isEmpty()) {  
			//we need to deploy new vm's
			// Order in which VMs are considered
			int vmCpuLoad,vmMemLOad;
			Collections.sort(vmsToDeploy,Collections.reverseOrder(new MinCPURequiredComparator()));
			for (VirtualMachine vm : vmsToDeploy) {
				Host host = getFirstFitHost(vm,cpuDemandOfVMs,memDemandOfVMs,tick);
				if(host != null){
					vmCpuLoad = Math.min(cpuDemandOfVMs.get(vm).intValue(),vm.getVMConfiguration().getMinCpu());
					vmMemLOad = memDemandOfVMs.get(vm).intValue();
					vmManager.setSatisfiedLoad(vm, vmCpuLoad, vmMemLOad);
					hostManager.addVmToHost(host,vm,vmCpuLoad, vmMemLOad);
					deployManager.setVmStartLife(vm, tick);
					placedNewVms.add(vm);
				}
				else{
					//TODO:add exception - why reject 
					System.out.println("rejected vm: "+ vm.getIdentifier());
					rejectedVms.add(vm);
				}
			}
			
		}
		
		updateLoadOnCloud(cpuDemandOfVMs,memDemandOfVMs);
		HistoryDemandArray[tick] = getTotalCpuDemand();
		//check if we need to open/close host
		openNewHost();
		if(isOngoingOptimizationTick) 
			optimizeCloud();
		updateVmMap();

		//create IPlacement
		validatePlacement();
		if(vmsToDeploy.size() != placedNewVms.size() +rejectedVms.size())
			System.out.println("Error at tick: "+tick);
		FireFightingPlacement newPlacement = new FireFightingPlacement(
				vmMapping, hostManager, lastPlacement, demandManager ,vmsToRemove,vmsToDeploy,placedNewVms,rejectedVms ,tick ,vmRelocate);
		//update end time
		engineStopTime = System.currentTimeMillis();
		return newPlacement;

	}	

	private void optimizeCloud() {
		int grad = calcCloudGraphGradient();
		//we estimate growing demand
		if(grad>=0)
			return;
		optimizeReduseLoadHost();
		optimizeCandidateForCloseHost();
		optimizeEvacuateHost();
		
	}
	
	private void optimizeReduseLoadHost() {
		int numberOfRelocation = configuration.numberOfRelocation;
		List<Host> openHosts = hostManager.getOpenHosts();
		for(Host host : openHosts) { 
			if(hostNeedToReduse(host)){
				if(reduseHostLoadUnderRedLine(host, numberOfRelocation)) {
					numberOfRelocation --;
				} else {
					return;
				}
				if(numberOfRelocation<=0) {
					return;
				}
			}
		}
	}

	private boolean reduseHostLoadUnderRedLine(Host hostToOptimize,int numberOfRelocation) {
		List<VirtualMachine> vmsOnHost = hostManager.getVmsAtHost(hostToOptimize);
		List<Host> openHost = hostManager.getOpenHosts();
		VirtualMachine vmToRelocate = null;
		int maxCpuDemand = 0;
		int vmMinDemand = 0;
		double targetHostCapcity,targetLoad,targetMaxLine;
		for(VirtualMachine vm : vmsOnHost) {
			if(!vmRelocate.contains(vm)) {
				vmMinDemand = vm.getVMConfiguration().getMinCpu();
				if(vmToRelocate == null || vmMinDemand>maxCpuDemand) {
					vmToRelocate = vm;
					maxCpuDemand = vmMinDemand;
				}
			}
		}
		for(Host host :openHost) {
			if(host.getHostId()!= hostToOptimize.getHostId()) {
				double sumMinCpuAtTargetHost = vmMinDemand;
				for(VirtualMachine vm : hostManager.getVmsAtHost(host)) {
					sumMinCpuAtTargetHost += vm.getVMConfiguration().getMinCpu();
				}
				targetHostCapcity = host.getHostCapacity().getCpuCapacity();
				if(sumMinCpuAtTargetHost < targetHostCapcity) {
					targetLoad = sumMinCpuAtTargetHost;
					targetMaxLine =  (configuration.MaxLine/100.0) * targetHostCapcity;
					if(targetLoad<targetMaxLine) {
						updateHostLoad(host,hostManager.getVmsAtHost(host),
								cpuDemandOfVMs,memDemandOfVMs);
						//relocate vm
						int sumMinDemand = 0;
						for(VirtualMachine vm : hostManager.getVmsAtHost(host)) {
							int minDEmand = vm.getVMConfiguration().getMinCpu();
							vm.setSatisfiedCpuDemand(minDEmand);
							sumMinDemand += minDEmand;
						}
						try {
							hostManager.setHostCurrentCpuLoad(host, sumMinDemand);
						} catch (Exception e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
							System.exit(1);
						} 
						vmRelocate.add(vmToRelocate);
						updateHostLoad(hostToOptimize,hostManager.getVmsAtHost(hostToOptimize),
								cpuDemandOfVMs,memDemandOfVMs);
						return true;
					}
				}
					
			}
		}
		return false;
	}

	private boolean hostNeedToReduse(Host host) {
		double hostLoad = host.getCurrentCpuLoad();
		double hostCapcity = host.getHostCapacity().getCpuCapacity();
		double maxLine = (configuration.MaxLine/100.0)*hostCapcity;
		double delta = maxLine - hostLoad;
		if(delta>0)
			return false;
		return true;
	}



	private void optimizeCandidateForCloseHost() {
		List<Host> openHosts = hostManager.getOpenHosts();
		for(Host host : openHosts) {
			if(hostNeedToEvacuation(host)) {
				if(!host.isCandidateForClosing())
					hostManager.makeHostCandidateForClosing(host, tick+configuration.numberOfTickCandidatesForClosing);
			}
		}
		
	}



	private void optimizeEvacuateHost() {
		List<Host> openHosts = hostManager.getOpenHosts();
		for(Host host : openHosts) {
			if(hostNeedToEvacuation(host)) {
				evacuatHost(host);
			}
		}
	}



	private boolean hostNeedToEvacuation(Host host) {
		//host not candidate for closing
		if(!host.isCandidateForClosing())
			return false;
		//host still have time
		if(host.getTickRemainingForClosing()>tick)
			return false;
		double hostLoad = host.getCurrentCpuLoad();
		double hostCapcity = host.getHostCapacity().getCpuCapacity();
		double hostMinline = (configuration.MinLine/100.0)*hostCapcity;
		double delta  = hostMinline - hostLoad;
		//host load above minimum line
		if(delta<0)
			return false;
		return true;
	}



	private void evacuatHost(Host hostToClose) {
		List<VirtualMachine> vmToEvacuat = hostManager.getVmsAtHost(hostToClose);
		boolean whileFlag = true;
		while(whileFlag) {
			boolean forFlag = false;
			for(Host host : hostManager.getOpenHosts()) {
				if(host.getHostId()!= hostToClose.getHostId()&& whileFlag && !host.isCandidateForClosing()) {
					int tmpScore = hostEvaluator.evaluationForHost(host, vmToEvacuat.get(0),
							hostManager.getVmsAtHost(host),cpuDemandOfVMs,memDemandOfVMs);
					if(tmpScore>0) {
						//min load on host
						double minLoadNewHost = vmToEvacuat.get(0).getVMConfiguration().getMinCpu();
						for(VirtualMachine vm : hostManager.getVmsAtHost(host)) {
							minLoadNewHost += vm.getVMConfiguration().getMinCpu();
						}
						double capcityNewHost = host.getHostCapacity().getCpuCapacity();
						double minLineNewHost = (configuration.MinLine/100.0)*capcityNewHost;
						double maxLineNewHost = (configuration.MaxLine/100.0)*capcityNewHost;
						double maxDelta = maxLineNewHost - minLoadNewHost;
						double minDelta = minLoadNewHost - minLineNewHost;
						if(maxDelta >0 && minDelta>0) {
							VirtualMachine vm = vmToEvacuat.remove(0);
							hostManager.relocateVm(vm, hostToClose, host,
									vm.getVMConfiguration().getMinCpu(),memDemandOfVMs.get(vm));
							updateHostLoad(host,hostManager.getVmsAtHost(host),
									cpuDemandOfVMs,memDemandOfVMs);
							vmRelocate.add(vm);
							forFlag = true;
							if(vmToEvacuat.size()==0)
								whileFlag = false;
						}
					}
				}
			}
			if(!forFlag)
				whileFlag = false;
		}
		if(vmToEvacuat.size()==0) {
			hostManager.closeHost(hostToClose);
		}
	}

//	private Host optimizeCandidatesForClosing (List<Host> allradyDone) {
//		List<Host> openHosts = hostManager.getOpenHosts();
//		double hostCapacity,hostMinLinePercent,hostLoad,hostLoadPercent,minSoFarOfLoad;
//		Host hostToEvacuate = null;
//		minSoFarOfLoad = Integer.MAX_VALUE;
//		for(Host host :openHosts) {
//			if(!allradyDone.contains(host)) {
//				hostCapacity = host.getHostCapacity().getCpuCapacity();
//				hostMinLinePercent = (configuration.MinLine/100.0) * hostCapacity ;
//				hostLoad = host.getCurrentCpuLoad();
//				hostLoadPercent = (hostLoad/hostCapacity)*100.0;
//				//chk if host load pass red line
//				if(hostLoadPercent<hostMinLinePercent) {
//					if(hostToEvacuate==null || minSoFarOfLoad > (hostMinLinePercent-hostLoadPercent)) {
//						hostToEvacuate = host;
//						minSoFarOfLoad = hostMinLinePercent-hostLoadPercent;
//					}
//				}
//			}
//		}
//		return hostToEvacuate;
//	}

	

//	private Host optimizeRedLine () {
//		List<Host> openHosts = hostManager.getOpenHosts();
//		double hostCapacity,hostRedLinePercent,hostLoad,hostLoadPercent,maxSoFarOfLoadToredu;
//		Host hostToReduceLoad = null;
//		maxSoFarOfLoadToredu = Integer.MIN_VALUE;
//		for(Host host :openHosts) {
//			hostCapacity = host.getHostCapacity().getCpuCapacity();
//			hostRedLinePercent = ((double)(configuration.MaxLine/100.0)) * hostCapacity ;
//			hostLoad = host.getCurrentCpuLoad();
//			hostLoadPercent = (hostLoad/hostCapacity)*100.0;
//			//chk if host load pass red line
//			if(hostLoadPercent>hostRedLinePercent) {
//				if(hostToReduceLoad==null || maxSoFarOfLoadToredu < (hostLoadPercent-hostRedLinePercent))
//					hostToReduceLoad = host;
//				maxSoFarOfLoadToredu = hostLoadPercent-hostRedLinePercent;
//			}
//		}
//		return hostToReduceLoad;
//	}
}